<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>视频帧抽取工具 - 精确帧率</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .container {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        .video-container, .canvas-container {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        video, canvas {
            max-width: 100%;
            background-color: #000;
        }
        .controls {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            align-items: center;
        }
        button {
            padding: 8px 16px;
            cursor: pointer;
        }
        .frame-info {
            margin-top: 10px;
        }
        .info-text {
            margin: 5px 0;
        }
    </style>
</head>
<body>
<div class="container">
    <h1>视频帧抽取工具 - 精确帧率</h1>

    <div class="video-container">
        <h2>视频源</h2>
        <input type="file" id="videoInput" accept="video/*">
        <video id="videoElement" controls></video>
    </div>

    <div class="info-text">视频信息: <span id="videoInfo">未加载</span></div>
    <div class="info-text">检测到的帧率(FPS): <span id="fpsInfo">未知</span></div>

    <div class="controls">
        <button id="prevFrame">上一帧</button>
        <button id="nextFrame">下一帧</button>
        <button id="extractFrame">抽取当前帧</button>
        <input type="number" id="frameNumber" placeholder="帧号" min="0">
        <button id="goToFrame">跳转到帧</button>
        <button id="extractRange">抽取帧范围</button>
    </div>

    <div class="frame-info">
        当前帧: <span id="currentFrame">0</span> /
        总帧数: <span id="totalFrames">0</span>
    </div>

    <div class="canvas-container">
        <h2>抽取的帧</h2>
        <canvas id="canvasElement"></canvas>
        <a id="downloadLink" style="display:none;">下载帧</a>
        <div id="batchDownload" style="display:none; margin-top:10px;"></div>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        const videoInput = document.getElementById('videoInput');
        const videoElement = document.getElementById('videoElement');
        const canvasElement = document.getElementById('canvasElement');
        const ctx = canvasElement.getContext('2d');
        const prevFrameBtn = document.getElementById('prevFrame');
        const nextFrameBtn = document.getElementById('nextFrame');
        const extractFrameBtn = document.getElementById('extractFrame');
        const frameNumberInput = document.getElementById('frameNumber');
        const goToFrameBtn = document.getElementById('goToFrame');
        const extractRangeBtn = document.getElementById('extractRange');
        const currentFrameSpan = document.getElementById('currentFrame');
        const totalFramesSpan = document.getElementById('totalFrames');
        const downloadLink = document.getElementById('downloadLink');
        const batchDownloadDiv = document.getElementById('batchDownload');
        const videoInfoSpan = document.getElementById('videoInfo');
        const fpsInfoSpan = document.getElementById('fpsInfo');

        let currentFrameIndex = 0;
        let videoDuration = 0;
        let frameRate = 0;
        let totalFrames = 0;
        let frameTimes = [];
        let isCalculatingFPS = false;

        // 加载视频文件
        videoInput.addEventListener('change', function(e) {
            const file = e.target.files[0];
            if (!file) return;

            const videoURL = URL.createObjectURL(file);
            videoElement.src = videoURL;

            videoElement.onloadedmetadata = function() {
                videoDuration = videoElement.duration;
                const resolution = `${videoElement.videoWidth}×${videoElement.videoHeight}`;
                videoInfoSpan.textContent = `${resolution}, ${videoDuration.toFixed(2)}秒`;

                // 设置canvas尺寸与视频相同
                canvasElement.width = videoElement.videoWidth;
                canvasElement.height = videoElement.videoHeight;

                // 开始计算实际帧率
                calculateActualFPS();
            };
        });

        // 计算实际帧率
        function calculateActualFPS() {
            if (isCalculatingFPS) return;
            isCalculatingFPS = true;
            frameTimes = [];

            // 先快速扫描视频以检测关键帧时间点
            videoElement.currentTime = 0;
            let lastTime = -1;
            let checkInterval = 0.1; // 检查间隔(秒)

            function checkFrames() {
                if (videoElement.currentTime >= videoDuration || frameTimes.length > 100) {
                    // 扫描完成，计算帧率
                    calculateFrameRateFromTimes();
                    isCalculatingFPS = false;
                    return;
                }

                if (Math.abs(videoElement.currentTime - lastTime) > 0.001) {
                    frameTimes.push(videoElement.currentTime);
                    lastTime = videoElement.currentTime;
                }

                videoElement.currentTime = Math.min(
                    videoElement.currentTime + checkInterval,
                    videoDuration
                );
            }

            videoElement.onseeked = checkFrames;
            checkFrames();
        }

        // 根据收集的时间点计算帧率
        function calculateFrameRateFromTimes() {
            if (frameTimes.length < 2) {
                // 无法计算，使用默认值
                frameRate = 30;
                fpsInfoSpan.textContent = `默认 ${frameRate} (无法检测)`;
                initializeFrameControls();
                return;
            }

            // 计算平均帧间隔
            let totalInterval = 0;
            for (let i = 1; i < frameTimes.length; i++) {
                totalInterval += frameTimes[i] - frameTimes[i - 1];
            }
            const avgInterval = totalInterval / (frameTimes.length - 1);
            frameRate = Math.round(1 / avgInterval);

            fpsInfoSpan.textContent = frameRate;
            initializeFrameControls();
        }

        // 初始化帧控制
        function initializeFrameControls() {
            totalFrames = Math.floor(videoDuration * frameRate);
            totalFramesSpan.textContent = totalFrames;
            currentFrameIndex = 0;
            currentFrameSpan.textContent = currentFrameIndex;
            frameNumberInput.value = currentFrameIndex;
        }

        // 上一帧
        prevFrameBtn.addEventListener('click', function() {
            if (currentFrameIndex > 0) {
                currentFrameIndex--;
                seekToFrame(currentFrameIndex);
            }
        });

        // 下一帧
        nextFrameBtn.addEventListener('click', function() {
            if (currentFrameIndex < totalFrames - 1) {
                currentFrameIndex++;
                seekToFrame(currentFrameIndex);
            }
        });

        // 跳转到指定帧
        goToFrameBtn.addEventListener('click', function() {
            const frameNum = parseInt(frameNumberInput.value);
            if (!isNaN(frameNum) && frameNum >= 0 && frameNum < totalFrames) {
                currentFrameIndex = frameNum;
                seekToFrame(currentFrameIndex);
            }
        });

        // 抽取当前帧
        extractFrameBtn.addEventListener('click', function() {
            if (videoElement.readyState === 0) return;

            // 绘制当前帧到canvas
            ctx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);

            // 创建下载链接
            const dataURL = canvasElement.toDataURL('image/png');
            downloadLink.href = dataURL;
            downloadLink.download = `frame_${currentFrameIndex}.png`;
            downloadLink.style.display = 'block';
            downloadLink.textContent = `下载帧 ${currentFrameIndex}`;
        });

        // 抽取帧范围
        extractRangeBtn.addEventListener('click', function() {
            const startFrame = prompt("输入起始帧号:", "0");
            const endFrame = prompt("输入结束帧号:", String(totalFrames - 1));

            if (startFrame === null || endFrame === null) return;

            const start = Math.max(0, parseInt(startFrame));
            const end = Math.min(totalFrames - 1, parseInt(endFrame));

            if (isNaN(start) || isNaN(end) || start > end) {
                alert("无效的帧范围");
                return;
            }

            batchDownloadDiv.innerHTML = "正在准备帧...";
            batchDownloadDiv.style.display = "block";

            // 使用setTimeout让UI更新
            setTimeout(() => {
                extractFrameRange(start, end);
            }, 100);
        });

        // 抽取帧范围
        function extractFrameRange(start, end) {
            batchDownloadDiv.innerHTML = "";
            const step = Math.max(1, Math.floor((end - start) / 100)); // 限制最多100帧

            let tempCanvas = document.createElement('canvas');
            tempCanvas.width = canvasElement.width;
            tempCanvas.height = canvasElement.height;
            let tempCtx = tempCanvas.getContext('2d');

            let current = start;

            function extractNextFrame() {
                if (current > end) {
                    batchDownloadDiv.innerHTML += "<br>所有帧已准备好!";
                    return;
                }

                seekToFrameForExtract(current, function() {
                    tempCtx.drawImage(videoElement, 0, 0, tempCanvas.width, tempCanvas.height);
                    const dataURL = tempCanvas.toDataURL('image/png');

                    const link = document.createElement('a');
                    link.href = dataURL;
                    link.download = `frame_${current}.png`;
                    link.textContent = `下载帧 ${current}`;
                    link.style.display = "block";
                    link.style.margin = "5px 0";

                    batchDownloadDiv.appendChild(link);
                    batchDownloadDiv.appendChild(document.createElement('br'));

                    current += step;
                    setTimeout(extractNextFrame, 0);
                });
            }

            extractNextFrame();
        }

        // 为抽取帧而跳转
        function seekToFrameForExtract(frameIndex, callback) {
            const time = frameIndex / frameRate;
            videoElement.currentTime = time;

            let seekCheck = function() {
                if (Math.abs(videoElement.currentTime - time) < 0.1) {
                    videoElement.removeEventListener('seeked', seekCheck);
                    callback();
                }
            };

            videoElement.addEventListener('seeked', seekCheck);
        }

        // 跳转到指定帧
        function seekToFrame(frameIndex) {
            if (!videoDuration) return;

            // 计算帧对应的时间点
            const time = frameIndex / frameRate;
            videoElement.currentTime = time;

            // 更新当前帧显示
            currentFrameSpan.textContent = frameIndex;
            frameNumberInput.value = frameIndex;
        }

        // 当视频时间更新时，更新canvas显示
        videoElement.addEventListener('timeupdate', function() {
            ctx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
        });
    });
</script>
</body>
</html>
